/*
 * msgstat.c
 * 
 * Copyright 2012 Kamil Cukrowski <kamil@dyzio.pl>
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation version 2.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 * 
 */
#define _VERSION "0.1.1"
#define _GNU_SOURCE 1 /* FOR vsprintf() defined in stdio.h needed __USE_GNU defining in features.h */
//#define DEBUG 1
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/sysinfo.h>
#include <fcntl.h>
#include <syslog.h>
#include <errno.h>
#include <signal.h>
#include <string.h>
#include <arpa/inet.h>
#include <stdarg.h>
#include <msgserv.h>
#define MY_NAME "msgmonitor"
#define PINFO(...) printf( __VA_ARGS__ );
#define PDEBUG(str, ...) PDEBUGL(1, str, ##__VA_ARGS__);
#define PDEBUGL(x, str, ...) do{ if ( debug >= x ) _INTERNAL_LOGIT(str, ##__VA_ARGS__); }while(0)
#define PERROR(str, ...) do { printf(MY_NAME ": (%s:%d): ", __FILE__, __LINE__); printf(str, ##__VA_ARGS__); printf("error %d:%s \n", errno, strerror(errno)); }while(0)
#define _INTERNAL_LOGIT(str, ...) do { printf( MY_NAME ": (%s:%d): ", __FILE__, __LINE__); printf(str, ##__VA_ARGS__); printf("\n"); }while(0)


/* ----------------- end of config ------------------- */

#define SSH_FUNCTION \
"function g(){ "\
"T=$1;shift;F=$@;"\
"for i in $F;do eval \"or$i=0;ot$i=0;nr$i=0;nt$i=0\";done;"\
"while sleep $T;do a=$(cat /proc/net/dev);m=\"\";"\
	"for i in $F;do "\
		"eval "\
			"\"b=\\$(echo \\\"\\$a\\\"|grep $i|sed \\\"s/$i://\\\");"\
			"nr$i=\\$(echo \\$b|awk \\\"{print \\\\\\$1}\\\");"\
			"nt$i=\\$(echo \\$b|awk \\\"{print \\\\\\$9}\\\");"\
			"m=\\\"\\$m\\$[\\$nr$i-\\$or$i] \\$[\\$nt$i-\\$ot$i] \\\";"\
			"or$i=\\$nr$i;ot$i=\\$nt$i;\";"\
	"done;"\
	"echo -e \"$m\";"\
"done;"\
"};g "


int debug = 0;

static char *pipe_file_name = NULL;

static int timeout = 10;
static int exec_timeout = 20;
static int wait_timeout = 10;

static char *ssh_connect = NULL;
static char *eth_names = NULL;
static char **info_string = NULL;
static int info_string_num = 0;
	
static in_addr_t server_ip = { 16777343 }; /* 127.0.0.1 */
static int sockfd = 0;
static int fpipe = 0;
static int kidpid = 0;

static int *st = NULL;
static int st_num = 0;

static void readable_net_size_3();
static void readable_net_size_4();
static void (*readable_net_size)(char *, unsigned long long) = readable_net_size_4;

static void kill_kid()
{
	if (kidpid) {
		kill(kidpid, 9);
		kidpid = 0;
	}
}

static void safe_exit()
{	
	if ( sockfd > 0 )
		msglcd_disconnect(sockfd);
	remove(pipe_file_name);
	kill_kid();
	close(fpipe);
	free(info_string);
	free(st);
        exit(0);
}
#define fatal(format, ...) do { PERROR(format, ##__VA_ARGS__); safe_exit(); } while (0)
#define perror(...) do{ PERROR( __VA_ARGS__ ); } while(0)
static void sig_hup (int param) {
	PDEBUG("Signal received. Going down");
	safe_exit();
}

static int __attribute__((format (printf, 3, 4))) 
	mysnprintf (char *s, int n, const char *format, ...)
{
	/* its same as snprintf
	 * but does not write '\0' on the end of the strin
	 * and returns number of chars *written*
	 * i dont even care about checking - be carefull */
	char msg[256];
	short int i;
	va_list va;
	
	if ( !s ) return 0;
	
	va_start (va, format);
	vsnprintf (msg, sizeof(msg), format, va);
	msg[sizeof(msg) - 1] = 0;
	va_end (va);
	
	for (i=0; i < n && i < 256 && i < strlen(msg); ++i) {
		s[i] = msg[i];
	}
	return(i);
}

static void parse_cmd(int argc, char *argv[])
{
	int c = 0;
	opterr = 0;
	
	while ( (c = getopt(argc, argv, "hdr:R:w:i:s:e:t:")) != -1 ) {
		switch (c) {
		case 'h':
			printf(
				"Msgmonitor: monitor my server netoutandin\n"
				"\t-h\t display this help.\n"
				"\t-d\t debug mode on (do not fork into the background);\n"
				"\t-r\t set refresh time in secends.\n"
				"\t-R\t exec_timeout - wait for reading from pipe \n"
				"\t-i\t server ip address.\n"
				"\t-w\t wait timeout - waiting before executing new ssh \n"
				"\t-s\t ssh_connect command \n"
				"\t-e\t eth_names on client computer(string) \n"
				"\t-t\t info string starting with no connection string, then increasing number(string) \n"
				"\t-N\t readable net size (int) \n"
			);
			exit(0);
		case 'd':
			debug++;
			break;
		case 'r':
			timeout = atoi(optarg);
			break;
		case 'R':
			exec_timeout = atoi(optarg);
			break;
		case 'w':
			wait_timeout = atoi(optarg);
			break;
		case 'i':
			server_ip = inet_addr(optarg);
			break;
		case 'V':
			printf(" VERSION: " MY_NAME " " _VERSION "\n");
			break;
		case 's':
			ssh_connect = optarg;
			break;
		case 'e':
			eth_names = optarg;
			break;
		case 't':
			if ( strlen(optarg) > 40 ) {
				printf("info string number %d too long \n", info_string_num);
				free(info_string);
				exit(-1);
			}
			info_string_num++;
			info_string = realloc(info_string, sizeof(info_string)*info_string_num);
			info_string[info_string_num-1] = optarg;
			break;
		case 'N':
			switch(atoi(optarg)) {
			case 3:
				readable_net_size = readable_net_size_3;
				break;
			case 4:
				readable_net_size = readable_net_size_4;
				break;
			default:
				printf("Wrong -N options \n");
				exit(-1);
			}
		}
	}
	
	if ( !ssh_connect || !eth_names )
		fatal("złe dane");
}

static int test_fd(const int fd, const char mode, const int tim_sec, const int tim_usec)
{
	/* mode = 'r' - test fd for incoming mesg
	 * mode = 'w' - test fd fot outgoing mesg (?)
	 * mode = 'b' - both [:-D */
	fd_set set;
	struct timeval tv;
	int ret;
	tv.tv_sec = tim_sec;
	tv.tv_usec = tim_usec;
	FD_ZERO(&set);
	FD_SET(fd, &set);
	switch (mode) {
	case 'r':
		ret = select(fd+1,&set,NULL,NULL,&tv);
	case 'w':
		ret = select(fd+1,NULL,&set,NULL,&tv);
	case 'b':
		ret = select(fd+1,&set,&set,NULL,&tv);
	}
	return ret;
}

static void stats_init(void)
{
	const char const *buff = eth_names;
	int i;
	
	if ( mkfifo(pipe_file_name, S_IRUSR| S_IWUSR) == -1 )
		if ( errno != EEXIST )
			fatal("could not create pipe");
	fpipe = open(pipe_file_name , 'r');
	if ( fpipe < 0 )
		fatal("could not open pipe");
	
	/* count the number of eths to display */
	st_num = 0;
	for(i=0; i<strlen(buff); i++)
		if ( buff[i] == ' ' )
			st_num++;
	st_num = 2*(st_num+1);
	
	/* alloc memory for handling needed int info */
	st = calloc(st_num, sizeof(*st));
	if ( !st )
		fatal("could not allocate memory");
}

static int stats_get_new_values(void)
{
	char c[200];
	char *pnt;
	int i;
	
	/* loop until getting one line */
	do {
		i = test_fd(fpipe, 'r', exec_timeout, 0);
		if ( i <= 0 )
			goto err_test_fd;
		i = read(fpipe, c, sizeof(c));
		if ( i < 0 ) 
			goto err_read;
		c[i] = '\0';

		if ( debug >= 3  )
			printf("msg:\"%s\"\n", c);

	} while (!strchr(c, '\n'));
	
	for(i=0, pnt = c; *pnt != '\n' && i < st_num; pnt = strchr(pnt, ' ')+1, i++) {
		sscanf(pnt , "%d ", &st[i]);
		st[i] *= 8;
	}
	
err_test_fd:
err_read:
	return i; /* success */
}

static int ssh_exec()
{
	char *pid_file_name, pnt[2500], buff[15];
	int fd, pid, i, size;
	
	pid_file_name = tempnam(NULL, NULL);
	
	size = sprintf(pnt,
		"%s "
		"-o ConnectTimeout=1 -o PasswordAuthentication=no -o PubkeyAuthentication=yes "
		"-o PreferredAuthentications=publickey "
		"'" SSH_FUNCTION " %d %s' 2>/dev/null >%s &"
		"echo $! > %s ;"
		,
		ssh_connect,
		timeout,
		eth_names,
		pipe_file_name,
		pid_file_name
	);
	if ( size == -1 )
		fatal(" could not allocate memory!! ");
	
	if ( debug >= 2 )
		printf("run$ %s", pnt);
	system(pnt);
	
	sleep(timeout/2);
	
	i = wait_timeout;
	do { 
		sleep(1);
		i--;
		fd = open(pid_file_name, 'r');
	} while( i && !fd );
	if ( !i )
		return -3;
	i = read(fd, buff, sizeof(buff));
	if ( i < 0 )
		return -2;
	pid = atoi(buff);
	close(fd);
	remove(pid_file_name);
	
	return pid;
}

static void ssh_new_exec()
{
	kill_kid();
	kidpid = ssh_exec();
}

static void readable_net_size_4(char *buff, unsigned long long N)
{
	double n = N;
	n /= timeout;
	if ( n < 1024 )
		mysnprintf(buff, 5, "%4.0fb", n);
	else if ( (n/=1024) < 10 )
		mysnprintf(buff, 5, "%1.2fK", n);
	else if ( n < 100 )
		mysnprintf(buff, 5, "%2.1fK", n);
	else if ( n < 1024 )
		mysnprintf(buff, 5, "%4.0fK", n);
	else if ( (n/=1024) < 10 )
		mysnprintf(buff, 5, "%1.2fM", n);
	else if ( n < 100 )
		mysnprintf(buff, 5, "%2.1fM", n);
	else if ( n < 1024 )
		mysnprintf(buff, 5, "%4.0fM", n);
	else if ( (n/=1024) < 10 )
		mysnprintf(buff, 5, "%1.2fG", n);
	else if ( n < 100 )
		mysnprintf(buff, 5, "%2.1fG", n);
	else if ( n < 1024 )
		mysnprintf(buff, 5, "%4.0fG", n);
	else if ( (n/=1024) < 10 )
		mysnprintf(buff, 5, "%1.2fP", n);
	else if ( n < 100 )
		mysnprintf(buff, 5, "%2.1fP", n);
	else if ( n < 1024 )
		mysnprintf(buff, 5, "%4.0fP", n);
	else
		mysnprintf(buff, 5, "%4.0f?", n);
}

static void readable_net_size_3(char *buff, unsigned long long N)
{
	double n = N;
	n /= timeout;
	if ( n < 1000 )
		mysnprintf(buff, 4, "%3.0fb", n);
	else if ( (n/=1024) < 1000 )
		mysnprintf(buff, 4, "%3.0fK", n);
	else if ( (n/=1024) < 1000 )
		mysnprintf(buff, 4, "%3.0fM", n);
	else if ( (n/=1024) < 1000 )
		mysnprintf(buff, 4, "%3.0fG", n);
	else if ( (n/=1024) < 1000 )
		mysnprintf(buff, 4, "%3.0fT", n);
	else if ( (n/=1024) < 1000 )
		mysnprintf(buff, 4, "%3.0fP", n);
	else 
		mysnprintf(buff, 4, "%3.0f?", n);
}

static void handle_msglcd_handle_server(const int lcd_sock) 
{
	int i;
	i = msglcd_handle_server(lcd_sock);
	switch ( i ) {
	case -1:
	case -5:
		fatal("Strange error occured %d ", i);
	case -4:
		fatal("Server closed the connection. %d ", i);
	case -6:
		fatal("server sended error messege %d", i);
	case -2:
		fatal("Server closed the connection without sending exit mesg. %d ", i);
	case -3:
		fatal("my mesg has somth wrong %d ", i);
	}
}

static void handle_msg_updat(const int sockfd, const char nr, const char pri, const char *mesg, const int len) 
{
	int i;
	i = msglcd_updat(sockfd, nr, pri, mesg, len);
	if ( i == 0 ) return;
	fatal("could not update mesg. msglcd_updat returned: %d", i);
}

static void wyswietl_no_connection(void)
{
	int lines = st_num/4;
	int i;
	
	
	char buff[41] = "          komp  no connection           ";
	if ( info_string_num >= 0 )
		snprintf(buff, 41, 
			"%40s", 
			info_string[0]
		);
	PDEBUG("%d %d ", st_num, lines);

	for(i=0; i < lines; i++) {
		
		handle_msg_updat(sockfd, i, MSG_DEBUG, buff, 40);
		PDEBUG("msg_updat %s", buff);
		
	}
	
	handle_msglcd_handle_server(sockfd);
}

static void wyswietl(void)
{
	int offset[4] = { 8, 15, 29, 35};
	int _st_num = st_num;
	int lines = _st_num/4;
	int i, j;
	
	for(i=0; i < lines; i++) {
		char buff[41] = "KOMP  >      <       | wew  >     <     ";
		
		if ( i+1 < info_string_num )
			snprintf(buff, 40, "%s", info_string[i+1]);
		
		for(j=0; j < (_st_num%4 == 0 ? 4 : _st_num%4 ); j++) {
			readable_net_size(buff+offset[j],  st[j+i*4]);
		}
		
		handle_msg_updat(sockfd, i, MSG_DEBUG, buff, 40);
		PDEBUG("%s", buff);
		_st_num -= 4;
		
	}
	
	handle_msglcd_handle_server(sockfd);
}

int main(int argc, char **argv)
{
	int ret;
	
	parse_cmd(argc, argv);
	
	if (!server_ip) {
		fprintf(stderr, "Please give msgsrv ip address.\n");
		safe_exit();
	}
	
	signal(SIGHUP,sig_hup);
	signal(SIGQUIT,sig_hup);
	signal(SIGTERM,sig_hup);
	signal(SIGINT,sig_hup);
	
	pipe_file_name = tempnam(NULL, NULL);
	
	sockfd = msglcd_connect(server_ip);
	if ( sockfd < 0 ) fatal("Could not connect to lcd server %d \n", sockfd);

	if ( debug <= 0 ) { 
		openlog(MY_NAME,LOG_PID,LOG_DAEMON);
		syslog(LOG_INFO,"Startup  succeeded.");
	}

	PDEBUG("Initialized with %s.", inet_ntoa(*(struct in_addr *)&server_ip));
	
	stats_init();
	
	wyswietl_no_connection();
	ssh_exec();
	
	for(;;) {
		ret = stats_get_new_values();
		if ( ret < 0 ) {
			perror(" PERROR ");
		} else if ( ret == 0 ) {
			wyswietl_no_connection();
			ssh_new_exec();
		} else {
			wyswietl();
		}
	}
	return 0;
}

